package com.zy.design.ddd.domain.wallet.service;

import com.zy.design.ddd.domain.wallet.entity.VirtualWallet;
import com.zy.design.ddd.domain.wallet.entity.VirtualWalletEntity;
import com.zy.design.ddd.domain.wallet.entity.VirtualWalletTransactionEntity;
import com.zy.design.ddd.domain.wallet.repository.VirtualWalletRepository;
import com.zy.design.ddd.domain.wallet.repository.VirtualWalletTransactionRepository;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;

/**
 * @author zhaoyi
 * @date 2021/2/5 16:04
 * 这里简化了没做接口
 * 基于充血模型的DDD
 * 1.Service 类负责与 Repository 交流。
 * 	在我的设计与代码实现中，VirtualWalletService类负责与 Repository 层打交道，
 *  调用 Respository 类的方法，获取数据库中的数据，转化成领域模型 VirtualWallet，
 *  然后由领域模型 VirtualWallet 来完成业务逻辑，最后调用Repository 类的方法，将数据存回数据库。
 *
 *2.Service 类负责跨领域模型的业务聚合功能。
 * 	VirtualWalletService 类中的 transfer() 转账函数会涉及两个钱包的操作，因此这部分业务逻辑无法放到 VirtualWallet 类中，
 * 所以，我们暂且把转账业务放到 VirtualWalletService 类中了。
 * 当然，随着功能演进，使得转账业务变得复杂起来之后，我们也可以将转账业务抽取出来，设计成一个独立的领域模型。
 *
 *3.Service 类负责一些非功能性及与三方系统交互的工作。
 * 	比如幂等、事务、发邮件、发消息、记录日志、调用其他系统的 RPC 接口等，都可以放到 Service 类中。
 *
 */
public class VirtualWalletService {

    // 模拟通过构造函数或者 IOC 框架注入
    private VirtualWalletRepository walletRepo = new VirtualWalletRepository();
    private VirtualWalletTransactionRepository transactionRepo = new VirtualWalletTransactionRepository();

    public VirtualWallet getVirtualWallet(Long walletId) throws InvocationTargetException, IllegalAccessException {
        VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
        VirtualWallet wallet = new VirtualWallet();
        BeanUtils.copyProperties(wallet, walletEntity);
        return wallet;
    }

    //出账
    public void debit(Long walletId, BigDecimal amount) throws InvocationTargetException, IllegalAccessException {
        VirtualWallet wallet = getVirtualWallet(walletId);
        wallet.debit(amount);
        walletRepo.updateBalance(walletId, wallet.getBalance());
    }

    //入账
    public void credit(Long walletId, BigDecimal amount) throws InvocationTargetException, IllegalAccessException {
        VirtualWallet wallet = getVirtualWallet(walletId);
        wallet.credit(amount);
        walletRepo.updateBalance(walletId, wallet.getBalance());
    }


    /**
     * 转账函数会涉及两个钱包的操作，因此这部分业务逻辑无法放到 VirtualWallet 类中，
     * 所以，我们暂且把转账业务放到 VirtualWalletService 类中了。
     * 当然，随着功能演进，使得转账业务变得复杂起来之后，我们也可以将转账业务抽取出来，设计成一个独立的领域模型。
     */
    public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) throws Exception {
        VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
        transactionEntity.setAmount(amount);
        transactionEntity.setCreateTime(System.currentTimeMillis());
        transactionEntity.setFromWalletId(fromWalletId);
        transactionEntity.setToWalletId(toWalletId);
        transactionEntity.setStatus("WAITING");
        //模拟保存交易记录
        Long transactionId = transactionRepo.saveTransaction(transactionEntity);
        try {
            debit(fromWalletId, amount);
            credit(toWalletId, amount);
        } catch (Exception e) {
            transactionRepo.updateStatus(transactionId, "FAILED");
            //异常事务回滚
            throw new RuntimeException("");
        }
        //最终一致性模型
        transactionRepo.updateStatus(transactionId, "SUCCESS");
    }
}
